import { Loader, relativePath, requestString } from "@loaders/commons";
import { MaterialLibrary } from "./material";
import { CacheData, PartType } from "./CacheData";
import { Mesh } from "./Mesh";

export class OBJLoader extends Loader {
  private _cacheData = new CacheData();
  private _canCreate = false;
  public meshes: Mesh[] = [];
  private _lastPart = null;
  private _lastGroup = "default";
  private _lastObject = "default";
  private _lastUseMtl = null;
  private _lastMtllib = null;
  constructor(public url: string, public parsedCallback: Function = () => {}) {
    super();
    requestString(url).then((data) => {
      // console.log(data);
      this.parseObjectData(data);
      // console.log(this.meshes);
    });
  }

  parseObjectData(objectData: string) {
    this._parseLine(objectData);
    this._toMeshes();
  }

  _toMeshes() {
    const v = this._cacheData.vLine;
    const vt = this._cacheData.vtLine;
    const vn = this._cacheData.vnLine;
    const vc = this._cacheData.vcLine;
    this._cacheData.parts.map((part, pIndex) => {
      let nMesh = new Mesh();
      nMesh.name = part.o;
      nMesh.group = part.g;
      nMesh.materialName = part.usemtl;
      let ind = 0;
      let hasDataLost = false;
      part.fLine.map((f) => {
        const triangles = this.triangulate(f);

        for (const triangle of triangles) {
          // 因索引类型限制，最大只能存两字节16bit的无符整数为 2^16=65536个, 所以直接使用索引，在大量点时会造成绘制数据丢失造成，表现为各种面镂空或连接不正确
          // 解决方法是将绘制方式改为非顶点绘制， 这里需要改造
          if (ind > 65535) {
            hasDataLost = true;
          }
          if (triangle.length === 3) {
            //
            nMesh.pushIndices([ind, ind + 1, ind + 2]);
            // }
          } else if (triangle.length === 4) {
            nMesh.pushIndices([ind, ind + 1, ind + 2]);
            nMesh.pushIndices([ind, ind + 2, ind + 3]);
          } else {
            debugger;
          }

          for (let j = 0, eleLen = triangle.length; j < eleLen; j++, ind++) {
            const vertex = triangle[j].split("/");

            nMesh.pushPositions(v[+vertex[0]]);

            if (vc.length > 1) {
              nMesh.pushColors(vc[+vertex[0]]);
            }

            if (vertex[1]) {
              nMesh.pushTexcoords(vt[+vertex[1]]);
            }

            if (vertex[2]) {
              nMesh.pushNormals(vn[+vertex[2]]);
            }
          }
        }
      });

      this.meshes.push(nMesh);
      if (hasDataLost) {
        nMesh.data.indices.length = 0;
        console.error("绘制索引超最大范围，将抛弃部分数据");
      }
    });

    if (this._cacheData.mtllib) {
      const nPath = relativePath(this.url, this._cacheData.mtllib);
      this.load(nPath)
        .then((response) => response.text())
        .then((data) => {
          //  = data;
          this._lastMtllib = new MaterialLibrary(data);
          this.meshes.forEach((mesh) => {
            mesh.material = this._lastMtllib.materials[mesh.materialName];
          });
          this.parsedCallback(this.meshes);
        });
    } else {
      this.parsedCallback(this.meshes);
    }
  }

  _parseLine(objectData: string) {
    const lines = objectData.split("\n");
    const keywordRE = /(\w*)(?: )*(.*)/;
    for (let line of lines) {
      line = line.trim();
      if (!line || line.startsWith("#")) {
        continue;
      }

      const m = keywordRE.exec(line);
      if (!m) {
        continue;
      }

      const [, keyword, unparsedArgs] = m;
      const parts = line.split(/\s+/).slice(1);
      const handler = this[`parse_${keyword}`];
      if (!handler) {
        console.warn("unhandled keyword:", keyword); // eslint-disable-line no-console
        continue;
      }

      handler.bind(this)(parts, unparsedArgs);
    }
  }

  triangulate(elements: number[]) {
    let arr = [];
    if (elements.length <= 3) {
      arr.push(elements);
    } else if (elements.length === 4) {
      arr.push([elements[0], elements[1], elements[2]]);
      arr.push([elements[2], elements[3], elements[0]]);
    } else {
      for (let i = 1; i < elements.length - 1; i++) {
        arr.push([elements[0], elements[i], elements[i + 1]]);
      }
    }
    return arr[Symbol.iterator]();
  }

  parse_v(parts, unparsedArgs) {
    if (parts.length > 3) {
      this._cacheData.vLine.push(parts.slice(0, 3).map(parseFloat));
      this._cacheData.vcLine.push(parts.slice(3).map(parseFloat));
    } else {
      this._cacheData.vLine.push(parts.map(parseFloat));
    }
  }

  parse_vc(parts, unparsedArgs) {
    this._cacheData.vcLine.push(parts.map(parseFloat));
  }

  parse_vt(parts, unparsedArgs) {
    this._cacheData.vtLine.push(parts.map(parseFloat));
  }

  parse_vn(parts, unparsedArgs) {
    this._cacheData.vnLine.push(parts.map(parseFloat));
  }

  parse_f(parts, unparsedArgs) {
    if (this._lastPart === null || this._canCreate === true) {
      this._canCreate = false;
      const part = new PartType();
      part.g = this._lastGroup;
      part.o = this._lastObject;
      part.usemtl = this._lastUseMtl;
      this._cacheData.parts.push(part);
      this._lastPart = part;
    }
    let len = this._cacheData.vLine.length;
    this._lastPart.fLine.push(parts);
  }

  parse_o(parts, unparsedArgs) {
    this._lastObject = unparsedArgs;
    this._canCreate = true;
  }

  parse_g(parts, unparsedArgs) {
    this._lastGroup = unparsedArgs;
    this._canCreate = true;
  }

  parse_usemtl(parts, unparsedArgs) {
    this._lastUseMtl = unparsedArgs;
    this._canCreate = true;
  }

  parse_mtllib(parts, unparsedArgs) {
    this._cacheData.mtllib = unparsedArgs;
  }
}
